using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using SevenZip.Compression.LZMA;

namespace SerializationUtilities
{
    public enum SerializationType
    {
        XML,
        Binary
    }

    public enum CompressionType
    {
        GZip,
        SevenZip,
        None
    }
    
    public class Utilities <T>
    {
        public Utilities() { }

        public Stream XMLSerializeData(T serializeObject)
        {
            //XML Serializer
            XmlSerializer xs = new XmlSerializer(typeof(T));
            //Memory stream to hold serialized XML
            MemoryStream ms = new MemoryStream();
            //Serialize XML to memory stream
            xs.Serialize(ms, serializeObject);
            //Reset Memory Stream Position
            ms.Seek(0, SeekOrigin.Begin);

            return ms;
        }

        public T XMLDeSerializer(Stream deserializeStream)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));

            T deserializedObject = (T)xs.Deserialize(deserializeStream);

            return deserializedObject;
        }

        public Stream BinarySerializeData(T serializeObject)
        {
            //Create Binary Formatter
            BinaryFormatter bf = new BinaryFormatter();
            //Memory stream to store outputed serialized data
            MemoryStream ms = new MemoryStream();
            //Serialize object
            bf.Serialize(ms, serializeObject);
            //Set stream position to beginning
            ms.Seek(0, SeekOrigin.Begin);
            //Return memory stream
            return ms;
        }

        public T BinaryDeSerialize(Stream deserealizeStream)
        {
            BinaryFormatter bf = new BinaryFormatter();

            T deserializedObject = (T)bf.Deserialize(deserealizeStream);

            return deserializedObject;
        }

        public void GZipCompressStream(Stream InStream, Stream OutStream)
        {
            //Copy Contents of Memory Stream into Byte Array
            byte[] zipbuffer = new byte[InStream.Length];
            InStream.Read(zipbuffer, 0, zipbuffer.Length);

            //Prepare To Compress Serialized XML
            GZipStream compressedzipStream = new GZipStream(OutStream, CompressionMode.Compress, true);
            //Compress and write to file
            compressedzipStream.Write(zipbuffer, 0, zipbuffer.Length);
            compressedzipStream.Close();
        }

        public void GZipDeCompressStream(Stream InStream, Stream OutStream)
        {
            //Decompresser
            GZipStream gzDecompressed = new GZipStream(InStream, CompressionMode.Decompress, true);

            //Retrieve the size of the decompressed file from the compressed footer
            byte[] bufferWrite = new byte[4];
            InStream.Position = (int)InStream.Length - 4;
            InStream.Read(bufferWrite, 0, 4);
            InStream.Position = 0;

            //Convert to int for using in declaring our Byte[] size
            int bufferLength = BitConverter.ToInt32(bufferWrite, 0);
            //1MB Buffer
            byte[] buffer = new byte[1024*1024];
            while (true)
            {
                int bytesRead = gzDecompressed.Read(buffer, 0, buffer.Length);

                // If we reached the end of the data
                if (bytesRead == 0) break;
                OutStream.Write(buffer, 0, bytesRead);
            }

            // Close the streams
            InStream.Close();
            gzDecompressed.Close();
            OutStream.Position = 0;
        }

        public void SevenZipCompressionStream(Stream InStream, Stream OutStream)
        {
            //Copy Contents of Memory Stream into Byte Array
            byte[] zipbuffer = new byte[InStream.Length];
            InStream.Read(zipbuffer, 0, zipbuffer.Length);
            //Compress InStream
            byte[] compressed = SevenZipHelper.Compress(zipbuffer);
            //Write to OutStream
            OutStream.Write(compressed, 0, compressed.Length);
        }

        public void SevenZipDeCompressionStream(Stream InStream, Stream OutStream)
        {
            //Copy Contents of Memory Stream into Byte Array
            byte[] zipbuffer = new byte[InStream.Length];
            InStream.Read(zipbuffer, 0, zipbuffer.Length);
            //Decompress
            byte[] uncompressed = SevenZipHelper.Decompress(zipbuffer);
            //Write to OutStream
            OutStream.Write(uncompressed, 0, uncompressed.Length);
            OutStream.Position = 0;
        }

        public void WriteStreamToFileStream(Stream SerializedData, Stream FileStream)
        {
            //Byte array to store stream data
            byte[] serialized = new byte[SerializedData.Length];
            //Write data to byte array
            SerializedData.Read(serialized, 0, serialized.Length);
            //Write to data to file stream
            FileStream.Write(serialized, 0, serialized.Length);
            //Close streams
            SerializedData.Close();
            FileStream.Close();
        }
        
        public void WriteObjectToFile(T writeObject, string FilePath, SerializationType SType, CompressionType CType)
        {
            WriteObjectToFile(writeObject, File.Open(FilePath, FileMode.Create), SType, CType);
        }

        public void WriteObjectToFile(T writeObject, Stream FileStream, SerializationType SType, CompressionType CType)
        {
            Stream serialized = Stream.Null;
            //Serialize data
            switch (SType)
            {
                case SerializationType.XML:
                    serialized = XMLSerializeData(writeObject);
                    break;
                case SerializationType.Binary:
                    serialized = BinarySerializeData(writeObject);
                    break;
            }
            //Compress and write to filestream
            switch (CType)
            {
                case CompressionType.GZip:
                    GZipCompressStream(serialized, FileStream);
                    break;
                case CompressionType.SevenZip:
                    SevenZipCompressionStream(serialized, FileStream);
                    break;
                case CompressionType.None:
                    //No compression, so write stream straight to file
                    WriteStreamToFileStream(serialized, FileStream);
                    break;
            }
        }

        public T ReadObjectFromFile(string FilePath, SerializationType SType, CompressionType CType)
        {
            return ReadObjectFromFile(File.Open(FilePath, FileMode.Open), SType, CType);
        }

        public T ReadObjectFromFile(Stream FileStream, SerializationType SType, CompressionType CType)
        {
            //To hold unzipped data
            MemoryStream unzipped = new MemoryStream();
            //Decompress file
            switch (CType)
            {
                case CompressionType.GZip:
                    GZipDeCompressStream(FileStream, unzipped);
                    break;
                case CompressionType.SevenZip:
                    SevenZipDeCompressionStream(FileStream, unzipped);
                    break;
            }
            //Unserialize data
            T UnSerializedData = default(T);

            switch (SType)
            {
                case SerializationType.XML:
                    UnSerializedData = XMLDeSerializer(unzipped);
                    break;
                case SerializationType.Binary:
                    UnSerializedData = BinaryDeSerialize(unzipped);
                    break;
            }
            //Close the FileStream
            FileStream.Close();

            return UnSerializedData;
        }
    }
}
